/*
 * This file is part of Applied Energistics 2. Copyright (c) 2013 - 2015, AlgorithmX2, All rights reserved. Applied
 * Energistics 2 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version. Applied Energistics 2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
 * Public License for more details. You should have received a copy of the GNU Lesser General Public License along with
 * Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
 */

package appeng.core.worlddata;

import java.io.File;
import java.util.List;
import java.util.concurrent.ThreadFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.config.Configuration;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import appeng.core.AEConfig;
import appeng.services.CompassService;
import appeng.services.compass.CompassThreadFactory;

/**
 * Singleton access to anything related to world-based data.
 * <p>
 * Data will change depending which world is loaded. Will probably not affect SMP at all since only one world is loaded,
 * but SSP more, cause they play on different worlds.
 *
 * @author thatsIch
 * @version rv3 - 02.11.2015
 * @since rv3 30.05.2015
 */
public final class WorldData implements IWorldData {

    private static final String AE2_DIRECTORY_NAME = "AE2";
    private static final String SETTING_FILE_NAME = "settings.cfg";
    private static final String SPAWNDATA_DIR_NAME = "spawndata";
    private static final String COMPASS_DIR_NAME = "compass";

    @Nullable
    private static IWorldData instance;

    private final IWorldPlayerData playerData;
    private final IWorldDimensionData dimensionData;
    private final IWorldGridStorageData storageData;
    private final IWorldCompassData compassData;
    private final IWorldSpawnData spawnData;

    private final List<IOnWorldStartable> startables;
    private final List<IOnWorldStoppable> stoppables;

    private final File ae2directory;
    private final File spawnDirectory;
    private final File compassDirectory;

    private final Configuration sharedConfig;

    private WorldData(@Nonnull final File worldDirectory) {
        Preconditions.checkNotNull(worldDirectory);
        Preconditions.checkArgument(worldDirectory.isDirectory());

        this.ae2directory = new File(worldDirectory, AE2_DIRECTORY_NAME);
        this.spawnDirectory = new File(this.ae2directory, SPAWNDATA_DIR_NAME);
        this.compassDirectory = new File(this.ae2directory, COMPASS_DIR_NAME);

        final File settingsFile = new File(this.ae2directory, SETTING_FILE_NAME);
        this.sharedConfig = new Configuration(settingsFile, AEConfig.VERSION);

        final PlayerData playerData = new PlayerData(this.sharedConfig);
        final DimensionData dimensionData = new DimensionData(this.sharedConfig);
        final StorageData storageData = new StorageData(this.sharedConfig);

        final ThreadFactory compassThreadFactory = new CompassThreadFactory();
        final CompassService compassService = new CompassService(this.compassDirectory, compassThreadFactory);
        final CompassData compassData = new CompassData(this.compassDirectory, compassService);

        final IWorldSpawnData spawnData = new SpawnData(this.spawnDirectory);

        this.playerData = playerData;
        this.dimensionData = dimensionData;
        this.storageData = storageData;
        this.compassData = compassData;
        this.spawnData = spawnData;

        this.startables = Lists.newArrayList(playerData, dimensionData, storageData);
        this.stoppables = Lists.newArrayList(playerData, dimensionData, storageData, compassData);
    }

    /**
     * @return ae2 data related to a specific world
     * @deprecated do not use singletons which are dependent on specific world state
     */
    @Deprecated
    @Nonnull
    public static IWorldData instance() {
        return instance;
    }

    /**
     * Requires to start up from external from here
     * <p>
     * drawback of the singleton build style
     */
    public static void onServerAboutToStart() {
        final File worldDirectory = DimensionManager.getCurrentSaveRootDirectory();
        final WorldData newInstance = new WorldData(worldDirectory);

        instance = newInstance;
        newInstance.onServerStarting();
    }

    private void onServerStarting() {
        // check if ae2 folder already exists, else create
        if (!this.ae2directory.isDirectory() && !this.ae2directory.mkdir()) {
            throw new IllegalStateException("Failed to create " + this.ae2directory.getAbsolutePath());
        }

        // check if compass folder already exists, else create
        if (!this.compassDirectory.isDirectory() && !this.compassDirectory.mkdir()) {
            throw new IllegalStateException("Failed to create " + this.compassDirectory.getAbsolutePath());
        }

        // check if spawn data dir already exists, else create
        if (!this.spawnDirectory.isDirectory() && !this.spawnDirectory.mkdir()) {
            throw new IllegalStateException("Failed to create " + this.spawnDirectory.getAbsolutePath());
        }

        for (final IOnWorldStartable startable : this.startables) {
            startable.onWorldStart();
        }

        this.startables.clear();
    }

    @Override
    public void onServerStopping() {
        for (final IOnWorldStoppable stoppable : this.stoppables) {
            stoppable.onWorldStop();
        }
    }

    @Override
    public void onServerStoppped() {
        Preconditions.checkNotNull(instance);

        this.stoppables.clear();
        instance = null;
    }

    @Nonnull
    @Override
    public IWorldGridStorageData storageData() {
        return this.storageData;
    }

    @Nonnull
    @Override
    public IWorldPlayerData playerData() {
        return this.playerData;
    }

    @Nonnull
    @Override
    public IWorldDimensionData dimensionData() {
        return this.dimensionData;
    }

    @Nonnull
    @Override
    public IWorldCompassData compassData() {
        return this.compassData;
    }

    @Nonnull
    @Override
    public IWorldSpawnData spawnData() {
        return this.spawnData;
    }
}
